﻿using ICSharpCode.SharpZipLib.Checksum;
using ICSharpCode.SharpZipLib.Zip;
using MiniFox.Components;
using MiniFox.Infrastructure;
using MiniFox.Platform.File.Models;
using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;

namespace MiniFox.Platform.File
{
    /// <summary>
    /// 
    /// </summary>
    [AutoRegister, SpareComponent(typeof(FileCompressor))]
    public class FileCompressor : IDisposable, IAsyncDisposable
    {
        const string ZIP_EXTENSION = ".zip";
        readonly object _locker;
        string _fileId;
        string _fileName;
        int _compressLevel;
        bool _autoCreateRoot;
        bool _executing;
        Stream _stream;
        ZipOutputStream _zipStream;
        ZipFileData _zipFileData;
        CancellationToken _cancelToken;

        #region 构造
        /// <summary>
        /// 
        /// </summary>
        /// <param name="compressLevel"></param>
        public FileCompressor(int compressLevel) : this(string.Empty, compressLevel)
        {

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        public FileCompressor(string fileName) : this(fileName, 9)
        {

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="compressLevel"></param>
        public FileCompressor(string fileName, int compressLevel) : this(fileName, compressLevel, !string.IsNullOrEmpty(fileName))
        {
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="compressLevel"></param>
        /// <param name="autoCreateRoot"></param>
        public FileCompressor(string fileName, int compressLevel, bool autoCreateRoot)
        {
            this._locker = new object();
            this._fileId = SequenceGUID.NewGUID();
            this._compressLevel = compressLevel;
            this._autoCreateRoot = autoCreateRoot;
            this._fileName = string.IsNullOrEmpty(fileName) ? _fileId : fileName;

            this._fileName = this._fileName.TrimEnd('.');
            if (!this._fileName.EndsWith(ZIP_EXTENSION, StringComparison.OrdinalIgnoreCase))
            {
                this._fileName += ZIP_EXTENSION;
            }

            this._executing = false;

            this._zipFileData = new ZipFileData
            {
                FileId = _fileId,
                FileName = _fileName
            };

            _cancelToken = new CancellationToken();
        }
        #endregion

        #region 属性
        /// <summary>
        /// 
        /// </summary>
        public string FileID
        {
            get { return _fileId; }
        }
        /// <summary>
        /// 
        /// </summary>
        public int CompressLevel
        {
            get { return _compressLevel; }
        }
        /// <summary>
        /// 
        /// </summary>
        public ZipFileData ZipFileData
        {
            get { return _zipFileData; }
        }
        /// <summary>
        /// 
        /// </summary>
        public string FileName
        {
            get { return this.ZipFileData.FileName; }
        }
        /// <summary>
        /// 
        /// </summary>
        public string DisplayName
        {
            get { return this.FileName.Replace(ZIP_EXTENSION, string.Empty); }
        }
        /// <summary>
        /// 
        /// </summary>
        protected virtual string ZipRootPath
        {
            get
            {
                string rootFolder = ZipFileData.DisplayName;
                return rootFolder;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public bool Executing
        {
            get { return _executing; }
        }

        private Stream BaseStream
        {
            get 
            {
                if (this._stream == null)
                {
                    this._stream = CreateBaseStream();
                }
                return this._stream; 
            }
        }
        #endregion
        /// <summary>
        /// 
        /// </summary>
        protected void Init()
        {
            if (this.Executing)
                return;
            Disposing();

            this._zipStream = new ZipOutputStream(this.BaseStream);
            this._zipStream.SetLevel(this.CompressLevel);
            this._executing = true;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected virtual Stream CreateBaseStream()
        {
            return new MemoryStream();
        }

        private void CreateZipEntry(PackFile packFile)
        {
            string zName = this._autoCreateRoot ? $"{this.ZipRootPath}/{packFile.FullName}" : packFile.FullName;
            ZipEntry entry = new ZipEntry(zName);
            entry.DateTime = DateTime.Now;

            if (packFile.HasContent)
            {
                entry.Size = packFile.ContentLength;

                Crc32 crc = new Crc32();
                crc.Reset();
                crc.Update(packFile.Data);
                entry.Crc = crc.Value;

            }
            this._zipStream.PutNextEntry(entry);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="packFile"></param>
        public void AddFileToCompress(PackFile packFile)
        {
            lock (_locker)
            {
                if (!this.Executing)
                    this.Init();
                this.ZipFileData.PackedFiles.Add(packFile);
                this.CreateZipEntry(packFile);

                if (packFile.HasContent)
                {
                    _zipStream.Write(packFile.Data, 0, packFile.Data.Length);
                    _zipStream.Flush();
                }
                _zipStream.CloseEntry();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="packFile"></param>
        /// <returns></returns>
        public async Task AddFileToCompressAsync(PackFile packFile)
        {
            if (!this.Executing)
                this.Init();
            this.ZipFileData.PackedFiles.Add(packFile);
            this.CreateZipEntry(packFile);
            if (packFile.HasContent)
            {
                await _zipStream.WriteAsync(packFile.Data, 0, packFile.Data.Length);
                await _zipStream.FlushAsync();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public void Finish()
        {
            if (!this.Executing)
                return;

            this._executing = false;

            if (!_zipStream.IsFinished)
            {
                _zipStream.Finish();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public async Task FinishAsync()
        {
            if (!this.Executing)
                return;

            this._executing = false;

            if (!_zipStream.IsFinished)
            {
                await _zipStream.FinishAsync(_cancelToken);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public ZipFileData GetResult()
        {
            this.Finish();

            using (var memoryStream = new MemoryStream())
            {
                this.BaseStream.Position = 0;
                this.BaseStream.CopyTo(memoryStream);
                this.ZipFileData.Data = memoryStream.ToArray();
            }

            return this.ZipFileData;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public async Task<ZipFileData> GetResultAsync()
        {
            this.Finish();

            using (var memoryStream = new MemoryStream())
            {
                this.BaseStream.Position = 0;
                await this.BaseStream.CopyToAsync(memoryStream);
                this.ZipFileData.Data = memoryStream.ToArray();
            }

            return this.ZipFileData;
        }
        /// <summary>
        /// 
        /// </summary>
        private void Disposing()
        {
            if (this._stream != null)
            {
                this._stream.Close();
                this._stream.Dispose();
            }
            if (this._zipStream != null)
            {
                this._zipStream.Close();
                this._zipStream.Dispose();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        private async ValueTask DisposingAsync()
        {
            if (this._stream != null)
            {
                this._stream.Close();
                await this._stream.DisposeAsync();
            }
            if (this._zipStream != null)
            {
                this._zipStream.Close();
                await this._zipStream.DisposeAsync();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            Finish();
            Disposing();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public async ValueTask DisposeAsync()
        {
            await FinishAsync();
            await DisposingAsync();
        }
    }

}
